/*
 * University Of Washington - Tacoma
 * TCSS-422, Operating Systems
 * Assignment 1 - Web Crawler
 * April 26, 2011
 * 
 * Team: Kernel Monkeys
 * Andrew Boguk
 * Sergiy Kulyk
 * Nicholas Swartzendruber
 */

package crawler.main;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import crawler.engine.Engine;
import crawler.engine.Reporter;

/**
 * Web crawler user interface.
 * This class prompts the user for the initial seed URL and starts
 * the crawler engine. When a report is ready, the DataGatherer will
 * call the reporterTriggered(Reporter) method to display the report. 
 * 
 * @author Sergiy Kulyk
 * @version April 24, 2011
 */
public class CrawlerUI implements UI, Observer {

	/**
	 * Prompt for the user to enter a URL.
	 */
	private static final String URL_PROMPT = 
		"\nEnter the seed URL (or leave the line blank to end the program): ";

	/**
	 * Message to the user that the entered URL is invalid.
	 */
	private static final String INVALID_URL = "The entered URL is invalid.";

	/**
	 * Prompt for the user to enter keywords.
	 */
	private static final String WORDS_PROMPT = 
		"Enter the keywords separated by space (up to 10): ";

	/**
	 * File error message.
	 */
	private static final String FILE_ERROR = "Cannot write to file.";

	/**
	 * File error message (at open).
	 */
	private static final String FILE_ERROR_WILL_CONTINUE = 
		"The program will continue without saving to disk.";

	/**
	 * File error message (at open).
	 */
	private static final String FILE_ERROR_CANNOT_OPEN = "Error: cannot open file ";

	/**
	 * Maximum number of accepted keywords.
	 */
	private static final int MAX_KEYWORDS = 10;
	
	/**
	 * File where to store output.
	 */
	private BufferedWriter my_file;
	
	/**
     * Console scanner for user input.
     */
    private final BufferedReader my_input;
    
    /**
     * Number of threads to run.
     */
    private final int my_threads_num;
    
    
    /**
     * The UI constructor. This method simply sets up member fields.
     * To run the UI (and the program) you should call the run() method.
     * 
     * @param the_threads_num Number of threads to start while retrieving 
     *                        and parsing web pages.
     */
    public CrawlerUI(final int the_threads_num, final String the_filename) {
    	
    	my_threads_num = the_threads_num;
    	my_input = new BufferedReader(new InputStreamReader(System.in));
    	//my_scanner = new Scanner(System.in).useDelimiter("\n|\r\n|\r");
    	
    	try{
    	    my_file = new BufferedWriter(new FileWriter(the_filename));
    	    
	    } catch (final Exception e) {
	    	
	      System.err.println(FILE_ERROR_CANNOT_OPEN + 
	    		  			 the_filename + 
	    		  			 FILE_ERROR_WILL_CONTINUE);
	      my_file = null;
	    }
    }


    /**
	 * When the engine finishes parsing a page, a
	 * new reporter object is passed back to the UI.
	 * This reporter object contains all the information
	 * about the pages that have been parsed up to
	 * a certain point. 
	 * 
	 * @param reporter The reporter object for all the
	 * 					information about parsed pages
	 * 					up to a certain point.
	 */
	@Override
	public synchronized void reporterTriggered(final Reporter the_reporter) {
		
		final String report = the_reporter.getFormattedData();
		
		try {
			if (my_file != null) {
				my_file.write(report);
				my_file.write("\n");
			}
		} catch (IOException e) {
			System.err.println(FILE_ERROR);
		}
		
		System.out.println(the_reporter.getFormattedData());
	}
	
	
	/**
	 * Run the UI. This method gets a URL from the user
	 * and runs the Engine with this URL as a seed. This
	 * process continues until the user enters a blank line,
	 * terminating the program.
	 */
	public void run() {
		try {
			
			String url = getURLFromUser(URL_PROMPT);		
			
			if (url.length() > 0) {
				
				if (isValidURL(url)) {
					Set<String> words = getWordsFromUser(WORDS_PROMPT);					
					
					if (my_file != null) {
						my_file.write("Seed URL: " + url + "\n");
						my_file.write("Keywords: ");
						for (String word : words) {
							my_file.write(word + " ");
						}
						my_file.write("\n\n");
					}
					
					final Engine engine = 
						new Engine(this, url, words, my_threads_num);
					engine.addObserver(this);
					engine.run();
				} else {
					System.out.println(INVALID_URL);
				}
				
			} else if (my_file != null) {				
				my_file.close();	
			}
			
		} catch (final IOException e) {
			
			System.err.println(FILE_ERROR);
		}
	}	
	
	
	/**
	 * Close the file after the program is finished.
	 */
	@Override
	public void update(Observable arg0, Object arg1) {
		
		try {			
			if (my_file != null) {				
					my_file.close();				
			}			
		} catch (IOException e) {
			System.err.println(FILE_ERROR);
		}
		
	}


	/**
	 * Get a set of keywords from the user.
	 * 
	 * @param the_prompt Prompt to display.
	 * @return Set of keywords.
	 */
	private Set<String> getWordsFromUser(final String the_prompt) {
		try {
			System.out.println(the_prompt);
			String[] words = my_input.readLine().trim().split(" ");
			Set<String> words_set = new HashSet<String>();
			
			// limit the number of keywords
			int words_num = words.length;
			if (words_num > MAX_KEYWORDS) {
				words_num = MAX_KEYWORDS;
			}
			
			for (int i = 0; i < words_num; i++) {
				words_set.add(words[i]);
			}		
			
			return words_set;
		
		} catch (final IOException e) {
			// unlikely stdin error
			e.printStackTrace();
			return null;
		}
	}


	/**
	 * Prompt the user for a value and return the entered string.
	 * 
	 * @param the_prompt Prompt to the user.
	 * @return User response.
	 */
	private String getURLFromUser(final String the_prompt) {
		try {
			System.out.println(the_prompt);
			String url = my_input.readLine().trim();
			if (url.length() > 0 && !url.startsWith("http://")) {
				url = "http://" + url;
			}        
	        return url;
		} catch (final IOException e) {
			// unlikely stdin error
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**
	 * Validate URL. This method checks the URL can be
	 * connected to by actually connecting to it.
	 * 
	 * @param the_url URL to check.
	 * @return True if the URL is correct, false otherwise.
	 */
	private boolean isValidURL(final String the_url) {
		
		boolean valid = false;
		
		try {
		    URL url = new URL(the_url);
		    URLConnection conn = url.openConnection();
		    conn.connect();
		    valid = true;
		} catch (final MalformedURLException e) {
			
		} catch (final NoSuchElementException e) {
		
		} catch (final IllegalArgumentException e) {
			
		} catch (final IOException e) {
			
		}
		
		return valid;
	}
	
}
